package com.slipper.system.flow.service;

import java.util.*;

import com.slipper.bpmn.BpmnHelper;
import com.slipper.bpmn.model.Bpmn;
import com.slipper.bpmn.model.Gateway;
import com.slipper.bpmn.model.Line;
import com.slipper.bpmn.model.TaskNode;
import com.slipper.constants.Constants;
import com.slipper.constants.FlowConstants;
import com.slipper.core.transaction.ReadTransactional;
import com.slipper.core.transaction.WriteTransactional;
import com.slipper.system.flow.bo.FlowLineBO;
import com.slipper.system.flow.dao.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import com.slipper.system.flow.bo.FlowBO;
import com.slipper.system.flow.bo.FlowNodeBO;
import com.slipper.util.DateUtil;
import com.slipper.util.ResultUtil;
import com.slipper.util.Tool;

@Service
@ReadTransactional
public class FlowService {
    private static final Logger logger = LoggerFactory.getLogger(FlowService.class);

    @Resource
    private FlowInstNodeDao flowInstNodeDao;
    @Resource
    private FlowInstDao flowInstDao;
    @Resource
    private FlowPendingDao flowPendingDao;
    @Resource
    private FlowDao flowDao;
    @Resource
    private FlowNodeDao flowNodeDao;
    @Resource
    private FlowLineDao flowLineDao;
    @Resource
    private FlowLineCondDao flowLineCondDao;

    public FlowBO queryBy(String id){
        return flowDao.queryBy(id);
    }
    
    public PageInfo<FlowBO> queryPage(FlowBO bo){
        PageHelper.startPage(bo.getPageNumber(), bo.getPageSize());
        return new PageInfo<>(flowDao.queryList(bo));
    }
    
    @WriteTransactional
    public String save(FlowBO bo){
        int result;
        if (bo.getId() != null && !"".equals(bo.getId())) {
            bo.setUpdateTime(System.currentTimeMillis());
            result = flowDao.update(bo);
        } else {
            bo.setId(Tool.getUUID());
            bo.setCode(this.createCode(bo));
            bo.setUpdateTime(System.currentTimeMillis());
            bo.setCreateTime(System.currentTimeMillis());
            result = flowDao.insert(bo);
        }
        
        return ResultUtil.successResult(result);
    }
    
    private String createCode(FlowBO bo) {
        FlowBO qo = new FlowBO();
        qo.setId(bo.getId());
        qo.setCode(DateUtil.format(new Date(), "yyyyMMdd"));
        List<FlowBO> flowList = this.flowDao.queryList(qo);
        if (flowList.isEmpty()) {
            qo.setCode(DateUtil.format(new Date(), "yyyyMMdd") + "01");
        } else {
            qo.setCode((flowList
                    .stream()
                    .map(FlowBO::getCode)
                    .mapToInt(Integer::parseInt)
                    .max().getAsInt() + 1) + "");
        }
        
        if (this.flowDao.queryExistCnt(qo) > 0) {
            return this.createCode(qo);
        } else {
            return qo.getCode();
        }
    }
    
    @WriteTransactional
    public Integer delete(FlowBO bo){
        if (Tool.isNotBlank(bo.getId())) {
            this.deleteById(bo.getId());
        }

        if (bo.getIds() != null) {
            for (String id : bo.getIds()) {
                this.deleteById(id);
            }
        }
        return 1;
    }

    @WriteTransactional
    public void deleteById(String id) {
        if (Tool.isBlank(id)) return;

        // 删除FlowInstNode
        this.flowInstNodeDao.deleteByFlowId(id);

        // 删除FlowPending
        this.flowPendingDao.deleteByFlowId(id);

        // 删除FlowInst
        this.flowInstDao.deleteByFlowId(id);

        // 删除FlowLineCond
        this.flowLineCondDao.deleteByFlowId(id);

        // 删除FlowLine
        this.flowLineDao.deleteByFlowId(id);

        // 删除 FlowNode
        this.flowNodeDao.deleteByFlowId(id);

        this.flowDao.deleteById(id);
    }

    @WriteTransactional
    public String saveFlowData(FlowBO bo) {
        this.saveFlowInfo(bo);
        return ResultUtil.saveResult(this.flowDao.updateFlowData(bo));
    }

    public List<FlowBO> queryList(FlowBO bo) {
        return flowDao.queryList(bo);
    }

    @WriteTransactional
    public void saveFlowInfo(FlowBO bo) {
        if (bo.getXmlText() == null || "".equals(bo.getXmlText())) {
            throw new RuntimeException("流程图数据不能为空！");
        }
        
        Bpmn bpmn = BpmnHelper.parser(bo.getXmlText());
        String errorMsg = bpmn.getErrorMsg();
        if (errorMsg != null && !errorMsg.isEmpty()) {
            logger.error("xml解释出错：{}", errorMsg);
            throw new RuntimeException(errorMsg);
        }
        
        // 处理节点
        Map<String, FlowNodeBO> nodeMap = new HashMap<>();

        // 开始节点
        FlowNodeBO startNode = new FlowNodeBO();
        BeanUtils.copyProperties(bpmn.getStartNode(), startNode);
        startNode.setFlowId(bo.getId());
        startNode.setOrdinal(0);
        startNode.setValid(Constants.YES);
        nodeMap.put(startNode.getCode(), startNode);
        
        // 结束节点
        FlowNodeBO endNode = new FlowNodeBO();
        BeanUtils.copyProperties(bpmn.getEndNode(), endNode);
        endNode.setFlowId(bo.getId());
        endNode.setOrdinal(9999);
        endNode.setValid(Constants.YES);
        nodeMap.put(endNode.getCode(), endNode);
        
        for (TaskNode taskNode : bpmn.getTaskNodeList()) {
            FlowNodeBO flowNode = new FlowNodeBO();
            BeanUtils.copyProperties(taskNode, flowNode);
            flowNode.setFlowId(bo.getId());
            flowNode.setValid(Constants.YES);
            nodeMap.put(flowNode.getCode(), flowNode);
        }
        for (Gateway gateway : bpmn.getGatewayList()) {
            FlowNodeBO flowNode = new FlowNodeBO();
            BeanUtils.copyProperties(gateway, flowNode);
            flowNode.setFlowId(bo.getId());
            flowNode.setValid(Constants.YES);
            nodeMap.put(flowNode.getCode(), flowNode);
        }

        this.saveFlowNode(bo.getId(), bpmn, nodeMap);
        this.saveFlowLine(bo.getId(), bpmn, nodeMap);
    }

    @WriteTransactional
    public void saveFlowNode(String flowId, Bpmn bpmn, Map<String, FlowNodeBO> nodeMap) {
        FlowNodeBO qo = new FlowNodeBO();
        qo.setFlowId(flowId);
        List<FlowNodeBO> flowNodeList = flowNodeDao.queryList(qo);
        List<FlowNodeBO> deleteNodeList = new ArrayList<>();
        for (FlowNodeBO oldNode : flowNodeList) {
            FlowNodeBO newNode = nodeMap.get(oldNode.getCode());
            if (newNode == null) { // 新流程表中不存在，此节点已经被删除
                oldNode.setValid(Constants.NO);
                deleteNodeList.add(oldNode);
            } else {
                newNode.setId(oldNode.getId());
                newNode.setValid(Constants.YES);
                newNode.setCreateTime(oldNode.getCreateTime());
            }
        }

        // 判断线路多少
        for (FlowNodeBO flowNode : nodeMap.values()) {
            if (Constants.YES.equals(flowNode.getValid())) {
                int inLine = 0;
                int outLine = 0;
                for (Line line : bpmn.getLineList()) {
                    if (line.getTargetRef().equals(flowNode.getCode())) {
                        inLine++;
                    }
                    if (line.getSourceRef().equals(flowNode.getCode())) {
                        outLine++;
                    }
                }
                if (FlowConstants.FlowNode.TYPE_START.equals(flowNode.getType())) {
                    if (inLine > 0) {
                        throw new RuntimeException("开始节点不允许有流入线！");
                    }
                    if (outLine != 1) {
                        throw new RuntimeException("开始节点必须有且仅有一条流出线！");
                    }
                } else if (FlowConstants.FlowNode.TYPE_TASK.equals(flowNode.getType())) {
                    if (inLine < 1) {
                        throw new RuntimeException("人工节点[" + flowNode.getName() + "]必须至少有一条流入线！");
                    }
                } else if (FlowConstants.FlowNode.TYPE_END.equals(flowNode.getType())) {
                    if (inLine < 1) {
                        throw new RuntimeException("结束节点必须至少有一条流入线！");
                    }
                    if (outLine > 0) {
                        throw new RuntimeException("结束节点不允许有流出线！");
                    }
                }
            }
        }

        // 保存/更新节点信息
        for (FlowNodeBO flowNode : nodeMap.values()) {
            if (flowNode.getId() == null) {
                flowNode.setId(Tool.getUUID());
                flowNode.setUpdateTime(System.currentTimeMillis());
                flowNode.setCreateTime(System.currentTimeMillis());
                this.flowNodeDao.insert(flowNode);
            } else {
                flowNode.setUpdateTime(System.currentTimeMillis());
                this.flowNodeDao.update(flowNode);
            }
        }

        // 删除节点
        for (FlowNodeBO flowNode : deleteNodeList) {
            this.flowNodeDao.delete(flowNode);
        }
    }

    @WriteTransactional
    public void saveFlowLine(String flowId, Bpmn bpmn, Map<String, FlowNodeBO> nodeMap) {
        Map<String, FlowLineBO> LineMap = new HashMap<>();
        for (Line line : bpmn.getLineList()) {
            FlowLineBO flowLine = new FlowLineBO();
            flowLine.setFlowId(flowId);
            flowLine.setPreNodeId(nodeMap.get(line.getSourceRef()).getId());
            flowLine.setNextNodeId(nodeMap.get(line.getTargetRef()).getId());
            flowLine.setCode(line.getCode());
            flowLine.setName(line.getName());
            flowLine.setRemark(line.getRemark());
            flowLine.setUpdateTime(System.currentTimeMillis());
            flowLine.setCreateTime(System.currentTimeMillis());
            LineMap.put(flowLine.getCode(), flowLine);
        }

        // 判断节点序是否在数据库中
        FlowLineBO fnQO = new FlowLineBO();
        fnQO.setFlowId(flowId);
        List<FlowLineBO> flowLineList = flowLineDao.queryList(fnQO);
        List<FlowLineBO> deleteLineList = new ArrayList<>();
        for (FlowLineBO oldLine : flowLineList) {
            FlowLineBO newLine = LineMap.get(oldLine.getCode());
            if (newLine == null) { // 新流程表中不存在，此节点序已经被删除
                deleteLineList.add(oldLine);
            } else {
                newLine.setId(oldLine.getId());
                newLine.setUpdateTime(System.currentTimeMillis());
                newLine.setCreateTime(oldLine.getCreateTime());
            }
        }

        // 保存/更新节点线路信息
        for (FlowLineBO flowLine : LineMap.values()) {
            if (flowLine.getId() == null) {
                flowLine.setId(Tool.getUUID());
                this.flowLineDao.insert(flowLine);
            } else {
                this.flowLineDao.update(flowLine);
            }
        }

        // 删除节点线路
        for (FlowLineBO flowLine : deleteLineList) {
            this.flowLineDao.delete(flowLine);
            flowLineCondDao.deleteByLineId(flowLine.getId());
        }
    }

    public FlowBO queryByCode(String code) {
        List<FlowBO> flowList = flowDao.queryByCode(code);
        return flowList.isEmpty() ? null : flowList.getFirst();
    }
}
